/* C.Dirscan: Get filenames for a wildcarded value */

/* Use as:
 *
 *	for (file = dirscan(wild); file; file = dirscan(0))
 *		...
 *
 * Specifying an argument other than 0 restarts the scan with the
 * new wildcard value.
 */ 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"

#include "utils.h"

/* Constants. The function assumes that a directory can hold no more than
 * MAX_DIR entries, and a filename can be no longer than NAME_LEN characters.
 * It also assumes that OS_GBPB 9 will only ever fail to return all possible
 * filenames on the first call if the supplied buffer becomes full. In this
 * case, dirscan() will ignore any omitted entries. This behaviour is not
 * ideal, but gains significantly in efficiency. (The given buffer size
 * should be easily enough for all normal cases).
 */
#define MAX_DIR		255
#define NAME_LEN	10
#define BUFFER_SIZE	(MAX_DIR * (NAME_LEN + 1))

char *dirscan (const char *f)
{
	int len;
	int dot_stripped = 0;
	const char *file;
	_kernel_swi_regs regs;

	static char *ptr = NULL;
	static int num_left = 0;
	static char *buffer = NULL;
	static char *name;
	static char res[255];

	if (f && *f)
	{

#ifdef DEBUG
printf("New call: looking at %s\n", f);
#endif

		/* Delete any old buffer */
		if (buffer)
		{
			free (buffer);
			buffer = NULL;
		}

		/* Try for a directory prefix */
		file = strrchr (f, '.');

		/* If not, try for a filing system prefix */
		if (file == NULL)
		{
			if (*f == '-')
				file = strchr (f + 1, '-');
			else
				file = strchr (f, ':');
		}

		if (file == NULL)
		{
			/* We have a simple file name, with no directory. So
			 * we set name to point to the place to store the
			 * file name (ie, res), and we temporarily set res
			 * to hold the directory name (ie, ""). We also set
			 * file to the file name f.
			 */
			name = res;
			file = f;
			name[0] = '\0';
		}
		else
		{
			/* More work to do. Set res up with the directory,
			 * as above, and point name to the place to put the
			 * file name(s). And set file to the file name.
			 */

			len = file - f + 1;

			/* Do not allow file names over 255 characters
			 * (ie, 245 for the directory, and 10 for the
			 * base name).
			 */
			if (len > 245)
				return 0;

			/* Build the start of the file name to be returned */
			strncpy (res, f, len);
			name = &res[len];

			/* Temporarily, put the name of the directory to scan
			 * into res. At the moment, res holds "dir." or "FS:"
			 * without a NULL terminator. If the former, we must
			 * temporarily remove the dot and add a NULL, but if
			 * the latter, we must add the current directory
			 * name "@".
			 */
			if (*file == '.')
			{
				dot_stripped = name[-1];
				name[-1] = '\0';
			}
			else
			{
				name[0] = '@';
				name[1] = '\0';
			}

			/* Now, set file to the base file name */
			++file;
		}

#ifdef DEBUG
printf("Split: dir = %s, file = %s\n", res, file);
#endif

		/* Is the file name wildcarded? */
		if (!strchr (file, '*') && !strchr (file, '#'))
		{
			/* If not, we return the name we were handed,
			 * and remember not to return any more later.
			 */

			/* First restore the proper prefix */
			if (dot_stripped)
				name[-1] = dot_stripped;

			strcpy (name, file);
			num_left = 0;
			return res;
		}
		else
		{
			/* OK, this is the big one. We set up a buffer, and
			 * read all the names from the selected directory
			 * which match the pattern into it.
			 */

			buffer = malloc (BUFFER_SIZE) ;
			if (buffer == NULL)
				return 0;

			regs.r[0] = 9;
			regs.r[1] = (int) res;
			regs.r[2] = (int) buffer;
			regs.r[3] = BUFFER_SIZE;
			regs.r[4] = 0;
			regs.r[5] = BUFFER_SIZE;
			regs.r[6] = (int) file;

			if (_kernel_swi (OS_GBPB, &regs, &regs))
				return 0;

#ifdef DEBUG
printf("Swi call succeeded: %d entries found\n", regs.r[3]);
#endif

			/* Now restore the proper prefix */
			if (dot_stripped)
				name[-1] = dot_stripped;

			/* If there were no entries to find, say so */
			if (regs.r[3] == 0)
			{
				num_left = 0;
				free (buffer);
				buffer = NULL;
				return 0;
			}

			/* Remember how many more to return and set ptr
			 * ready to return the next one.
			 */
			num_left = regs.r[3] - 1;
			ptr = buffer + strlen (buffer) + 1;

#ifdef DEBUG
printf("Buffer set up: first = %s, next = %s\n", buffer, ptr);
#endif

			/* Return the first one */
			strcpy (name, buffer);
			return res;
		}
	}

	/* If we've run out of names to return, free the buffer and stop */
	if (num_left == 0)
	{
		if (buffer)
		{
			free (buffer);
			buffer = NULL;
		}
		return 0;
	}


	/* Return the next entry in the buffer, and move on in the buffer */
	strcpy (name, ptr);
	ptr += strlen(ptr) + 1;
	--num_left;

#ifdef DEBUG
printf("Skipped on to %s\n",ptr);
#endif

	return res;
}

#ifdef TEST
#include <stdio.h>

int main (int argc, char *argv[])
{
	char *name;

	while (--argc > 0)
	{
		printf("Scanning %s\n", *++argv);
		for (name = dirscan(*argv); name; name = dirscan(0))
			printf("%s\n", name);
	}

	return 0;
}
#endif
